home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Macintosh Bible Guide to Games
/
GameGuideCd.bin
/
Shareware
/
Miscellaneous
/
tads22 folder
/
TADSVER.MAC
< prev
Wrap
Text File
|
1994-09-24
|
94KB
|
2,042 lines
This file contains a list of changes that have been made to TADS
since the version 2.1.0 release. Most of the changes are fixes to
bugs, so they don't change the documented behavior, but a few, as
explained below, add new functionality to TADS. Releases are
listed with the most recent release first; each release incorporates
all new features and bug fixes of each prior release unless
otherwise stated.
Note: changes from version 2.0 up to 2.1.0 are not included in
this file in order to keep its size under control. The revisions
from 2.0 through 2.1.0 are available in the file TADSV200.DOS, which
can be found on the High Energy BBS.
2.2.0 ??/??/?? new features, enhancements, bug fixes
- TADS now has support for reading and writing files. This new
feature is intended to let you save information independently
of the game-saving mechanism, which allows you to transfer
information between sessions of a game, or even between two
different games. The TADS file operations are not designed
as general-purpose file system operations; in particular,
these new functions don't have any provisions for creating or
reading formatted files, or for exchanging information with
programs other than TADS games.
To open a file, use the fopen() function. This function takes
two arguments: a single-quoted string giving the name of the
file to open, using local file system conventions, and a "mode."
(For maximum portability, you should avoid using volume names,
directories, folders, or other path information in filenames.)
The mode argument is one of these single-quoted string values:
r open file for reading; file must already exist
r+ open file for reading and writing; the file is
created if it doesn't already exist
w create a new file for writing; the file is deleted
if it already exists
w+ create a new file for reading and writing; the file
is deleted if it already exists
The return value of fopen() is a "file handle"; this is simply
a number that you you to perform subsequent operations on the
file. For example, this opens a new file called TEST.OUT for
writing:
fnum := fopen('test.out', 'w');
To close an open file, use fclose():
fclose(fnum);
Note that the TADS runtime allows only a limited number of
files (currently 10) to be open simultaneously, so you should
close a file when you're done with it.
To write to a file, use fwrite(). This function takes a file
handle, and a value to write; the value can be a string, a number,
or true. The value can't be nil (this is because the fread() function
returns nil to indicate failure; if you could write nil to a file,
there would be no way to distinguish reading a valid nil from an
error condition). fwrite() stores the value, along with information
on its type.
The fwrite() function returns nil on success, true on failure. If
the function returns true, it usually means that the disk is full.
if (fwrite(fnum, 'string value!')
or fwrite(fnum, 123))
"Error writing file!";
If the file is open for reading, you can read from the file with
the fread() function. This function takes a file handle, and it
returns a value it reads from the file. The value returned is
of the same type as the value originally written at this position
in the file with fwrite(). If this function returns nil, it
indicates that an error occurred; this usually means that no more
information is in the file (you've read past the end of the file).
res := fread(fnum);
say(res);
You can get the current byte position in the file with the ftell()
function:
"The current seek position is << ftell(fnum) >>. ";
The ftell() function returns a number giving the byte position
that will be read or written by the next file operation.
You can set the file position with fseek() and fseekeof(). The
fseek() function moves the file position to a particular byte
position, relative to the beginning of the file. For example,
this seeks to the very beginning of a file:
fseek(fnum, 0);
The fseekeof() function positions the file at its end:
fseekeof(fnum);
Note that you must be careful with fseek(). You should only seek
to positions that you obtained with the ftell() function; other
positions may be in the middle of a string or a number in the
file, so seeking to an arbitrary location and writing could
render the file unusable by partially overwriting existing data.
- TADS now allows you to create and delete objects dynamically
at run-time. This is done through two new operators: "new"
and "delete".
To create a new object, use this syntax:
x := new bookItem;
This dynamically creates a new object whose superclass is bookItem.
When this statement is executed, the runtime creates a new object,
assigns its superclass to be bookItem, and executes the "construct"
method in the new object; this method can perform any creation-time
setup that's desired. The default thing.construct in adv.t simply
moves the new object into its location -- this is necessary so that
the "contents" list of the location is updated to include the new
object.
A new object inherits all of the vocabulary of its superclass.
To destroy an object you have created, use this syntax:
delete x;
This first calls the "destruct" method of the object to notify it
that it is about to be deleted, then destroys the object. Further
references to the object are illegal, since its memory has been
released (and thus may be given to another object). The default
thing.destruct in adv.t moves the object into nil, which removes it
from its container's "contents" list -- this is necessary so that
the reference to the object in that list is removed.
Only objects created with "new" can be destroyed with "delete".
Objects that are defined statically in your game's source file
cannot be deleted at run-time.
Object creation and deletion works correctly with the UNDO
mechanism. If the player uses UNDO after a move that created an
object, the object will be destroyed; likewise, if a player uses
UNDO after a turn that deletes an object, the object will be
re-created with the same property values it had prior to deletion.
Similarly, dynamically-created objects are preserved across SAVE
and RESTORE operations.
Note that TADS does not perform any garbage collection on
dynamically-created objects. The system is not capable of
determining whether an object is accessible or not. Hence, if
you lose track of any objects you create with "new", they will
remain in memory forever -- they will even be saved along with
saved games and restored when the games are restored. You must
be careful to keep track of all objects you create to avoid
filling all available memory (and the swap file) with unreachable
objects.
- It is now possible to dynamically add to and delete from the
vocabulary words of an object. You can also get the vocabulary
words of an object at run-time.
To add to an object's vocabulary, use the new "addword" built-in
function. This function takes three arguments: an object, a
vocabulary property pointer, and a word to add. For example,
to add 'red' as an adjective to the object myBook, you would
do this:
addword(myBook, &adjective, 'red');
To delete the same word, you would write a similar call to the
new built-in function "delword":
delword(myBook, &adjective, 'red');
You can add to and delete from the words of any object, including
both static objects (explicitly defined in your source code) and
dynamically-created objects (created with the "new" operator).
Changes made by addword and delword are tracked correctly by the
UNDO mechanism, and are saved and restored along with saved games.
To get the words belonging to an object, use the new "getwords"
built-in function. This function takes two arguments: an object,
and a property pointer; it returns a list of (single-quoted)
strings, which are the vocabulary words for the object. For
example, assume we define myBook as follows:
myBook: item
sdesc = "small red book"
adjective = 'small' 'red' 'tiny'
noun = 'book'
location = room2
;
Also assume we haven't made any calls to addword() or delword() for
myBook. In this case,
getwords(myBook, &adjective)
would return this list:
['small' 'red' 'tiny']
Note that the order of the words in the list is not predictable,
so you shouldn't expect the words to be in the same order as they
were when you defined them in the source file, or in the same
order as they were added with addword().
- We've added a new function that lets you get information on a verb.
The new function is verbinfo(). This function lets you get the
verification and action properties for a verb. The new function
takes one or two arguments: the first is the deepverb object whose
information you want to retrieve; the optional second argument is
a preposition object. If you call verbinfo() with only the verb
argument, it returns the verification and action properties that
are defined with the doAction definition for the verb. If you
also include the preposition argument, it returns the properties
that are defined with the ioAction definition for that preposition.
The value returned by this function is a list. If you call verbinfo()
with only the deepverb argument, the list has two elements:
[1] direct object verification property pointer (verDoXxxx)
[2] direct object action property pointer (doXxxx)
If you call verbinfo() with both the verb and preposition arguments,
the return value is a list with four elements:
[1] direct object verification property pointer (verDoXxxx)
[2] indirect object verification property pointer (verIoXxxx)
[3] indirect object action property pointer (ioXxxx)
[4] true if ioAction has [disambigDobjFirst] flag, nil otherwise
In either case, if no matching doAction or ioAction definition
exists for the verb, this function returns nil.
Note that it is possible that additional flags (similar to
disambigDobjFirst) may be added in the future; the returned list
may be expanded to include information on any such added flags.
So, for compatibility with future versions, we recommend that you
don't write conditional code based on the length of the list.
The lists will never shrink, but they may expand.
For the removeVerb object defined in adv.t, you would get these
results:
verbinfo(removeVerb)
= [&verDoUnwear &doUnwear]
verbinfo(removeVerb, fromPrep)
= [&verDoRemoveFrom &verIoRemoveFrom &ioRemoveFrom nil]
- The ability to create new objects at run-time leads to some
interesting problems involving indistinguishable objects. Although
you should generally use addword (see below) to make your newly-created
objects distinguishable from one another, this will not always be
desirable; for example, if you create new gold pieces that serve
as currency, you will probably not want them to be uniquely named.
To support indistinguishable objects, especially those created
dynamically at run-time, the system now has a property that you
can set to indicate to the parser that an object does not need to
be distinguished from others of the same class. The new property
is "isEquivalent". When isEquivalent returns true for an object,
all other objects with the same immediate superclass are considered
interchangeable by the parser. When a player uses one of these
objects in a command, the parser will simply pick one arbitrarily
and use it, without asking the player which one.
If a player uses a noun that is ambiguous with multiple equivalent
items and one or more other items, the parser will need to
disambiguate the objects as usual. In such cases, the parser's
question will list the distinguishable items only once. For
example, assume we have five gold coins that are all equivalent
(in other words, they all have isEquivalent set to true, and they
all are immediate subclasses of the same class). Assume further
that a silver coin and a bronze coin are also present in the room.
Treasure Room
You see a bronze coin, five gold coins, and a silver
coin here.
>get coin
Which coin do you mean, the bronze coin, a gold coin, or
the silver coin?
Note that the objects which appear only once are listed with "the"
(using the thedesc property), while the indistinguishable objects
are listed only once, with "a" (using the adesc property).
- The new property pluraldesc has been added to thing in adv.t.
The definition in thing simply adds an "s" to the end of the
sdesc property. This new property is used by listcont(obj)
when multiple equivalent objects are present in a list; see the
information on the changes to listcont(obj) for details.
- The adv.t functions listcont(obj) and itemcnt(list) have been
changed to support indistinguishable objects. To support this
new functionality, the new functions isIndistinguishable(obj1, obj2)
and sayPrefixCount(cnt), and the new property pluraldesc, have
been added to adv.t.
isIndistinguishable(obj1, obj2) returns true if the two objects
obj1 and obj2 are equivalent for the purposes of listing. The
two objects are considered equivalent if both have the same
first superclass (the return value of the new built-in function
firstsc(obj)), either they are both being worn or neither is
worn, and either both are lit lightsources or neither is. This
function doesn't test the isEquivalent property of either object,
since it's assumed that it will only be called if an object has
already been found whose isEquivalent property is set.
sayPrefixCount(cnt) displays a number. If the parameter cnt is
a small number (from one to twenty), the spelled-out number will
be displayed (for example, "five" will displayed if cnt = 5). If
the count is larger, the number will be displayed as digits ("35"
will be displayed if cnt = 35). This function is used by
listcont(obj) to show the number of equivalent items when more
than one equivalent item is being listed.
itemcnt(list) now returns the number of distinguishable items in
the list that are to be listed. For each item in the list whose
isEquivalent property is true, itemcnt(list) checks each other
item in the list, and counts each set of equivalent items only once.
Hence, if a list consists entirely of equivalent items, itemcnt(list)
will return at most 1 (it will return 0 if none of the items are
listable).
listcont(obj) will list each set of indistinguishable items in the
contents list only once, and will show the number of each such item.
To display the number, the new function sayPrefixCount(cnt) is
used. For example, if a room contains a silver coin, a bronze coin,
and five gold coins (all with the isListed property set to true),
listcont(room) will display this:
a silver coin, a bronze coin, and five gold coins
- A new built-in function has been added to make it possible to
implement the functionality of the new listcont method. The new
function, firstsc(obj), returns the first immediate superclass of
the given object (or nil if the object has no superclass). This
function, along with the isEquivalent property, can be used to
determine if two objects should be considered indistinguishable.
- A new special word has been added: ANY, which is equivalent to
EITHER. These words are at the end of the original specialWords
list; for compatibility with past versions, a specialWords list
that omits this position is still legal, and indicates that the
default ('any' = 'either') should be used for this slot.
This special word slot is used by the parser during disambiguation.
Whenever the parser asks the player to choose an object from a list
of ambiguous objects, it will accept ANY:
>take coin
Which coin do you mean, the silver coin, the bronze coin, or
the gold coin?
>any
silver coin: Taken.
When ANY is used in these cases, the parser will simply pick one
of the objects arbitrarily. Note that it displays the chosen
object in the same manner as it would if multiple objects were
being used.
In addition, the parser will accept noun phrases that start with
ANY to indicate that any object matching the given noun phrase is
acceptable; the parser will choose one of the objects arbitrarily
in these cases. For example:
>take any coin
silver coin: Taken.
>look at any of the coins
bronze coin: It's a valuable 1964 Tadsmid, worth over
.0004 cents on today's scrap bronze market.
The player can also specify the number of items to take. When
a number of items is specified, it must be applied to a plural
noun phrase, and it means the same thing as "any," except that
the parser (arbitrarily) chooses the given number of items rather
than just one. For example:
>look at 3 coins
>look at any 3 coins
>look at 3 of the coins
>look at any 3 of the coins
As a special case, a count of "1" can be used with a singular
noun phrase. It means the same thing as "any."
>look at 1 coin
- A new convenience feature, similar to doSynonym and ioSynonym but
somewhat easier to use, has been added. You can now specify that
a method in one object should instead be sent to another object.
An example:
desk: fixeditem
noun = 'desk'
sdesc = "desk"
location = office
doOpen -> deskDrawer
doClose -> deskDrawer
;
This specifies that doOpen, verDoOpen, doClose, and verDoClose
calls should be sent to the deskDrawer object when received by
the desk. Note that this should only be used for standard verb
handler methods, because it redirects both the method indicated
and its verXoVerb equivalent.
- The parser calls a new method, multisdesc, when displaying the
name of an object that's part of a list of objects. Previously,
the parser simply used sdesc in these cases. The default adv.t
definition of thing.multisdesc simply calls the object's sdesc.
This new method is intended to allow you greater control over
the display in situations like this:
>get all
book: Taken.
rug: That's much too heavy to carry.
The object names listed before the colons are now displayed
with multisdesc.
For compatibility with old games, if an object being listed does
not define or inherit a multisdesc property, its sdesc is used
instead. This ensures that games compiled with previous versions
of adv.t will continue working properly.
- A new user-defined function that the parser calls has been added.
This new function is called preparseCmd(), and is similar to
preparse(). Whereas preparse() is called once for an entire
command line, and is called with the original, unfiltered text
of the player's command line, preparseCmd() is called separately
for each command on a command line if more then one command is
entered. Furthermore, preparseCmd() is called after the command
has been "tokenized" (broken into individual words). Whereas the
argument to preparse() is a string with the entire command line,
the argument to preparseCmd() is a list, each entry of which is
a (single-quoted) string giving an individual word. Using the new
function, you can exert much greater control over how a command
is parsed, including rewriting a command entirely.
preparseCmd() is called immediately before Me.roomCheck() is called
for the command. This call is made prior to any disambiguation or
object defaulting.
If preparseCmd() returns nil, the command is abandoned (but no
error message is displayed), and no fuses or daemons are run. If
the function returns true, the command proceeds as normal. Any
remaining commands on the same line are executed regardless of the
return value from preparseCmd(). (If you need to cancel the entire
rest of the command line, one approach would be to set a property
in the global object to indicate to preparseCmd() that all commands
are to be ignored; preparseCmd() would always check this property
before doing anything else, and return nil if it were set. You
could clear this property in preparse() so that commands always
start off enabled.)
In addition, preparseCmd() can return a list of (single-quoted)
strings, in which case the parser starts over parsing the list
instead of the original command. The list is limited to a maximum
of 128 characters, with one additional character of overhead per
word -- in other words, you can't make the new command longer
than the original command, which is limited to the same maximum
when entered by the player in the first place. However, you can
otherwise rewrite the command entirely. If you want to include
any special words in the new command, use the conventions described
below (for example, if you want to include 'and' in the new command,
use ',' instead). After the new command inserted by preparseCmd()
has been processed, the parser will resume processing any remaining
commands on the player's original command line.
The new command list returned by preparseCmd() can contain multiple
commands. Simply separate the commands with commas (',') in your
list.
If preparseCmd() returns a list, preparseCmd() will be invoked on
the new command. However, preparseCmd() is not allowed to return
another new command in these cases -- if it does, the parser will
assume that preparseCmd() is looping, and will generate an error.
Several new parseError codes have been added to respond to error
conditions that can arise from preparseCmd():
32 Internal game error: preparseCmd returned an invalid list
33 Internal game error: preparseCmd command too long
34 Internal gmae error: preparseCmd loop
The sample implementation of preparseCmd() below simply lists all
of the words in the current command and displays a newline, then
allows the command to proceed as usual.
#pragma C+
preparseCmd: function(cmd)
{
local i, tot;
for (i = 1, tot = length(cmd) ; i <= tot ; ++i)
"<<cmd[i]>> ";
"\n";
return true;
}
Note that the parser performs conversions of the special words.
These conversions will show up in the list as follows:
"and" becomes ","
"all" becomes "A"
"but" becomes "X"
"it" becomes "I"
"them" becomes "T"
"him" becomes "M"
"her" becomes "R"
"any" becomes "Y"
Here are some examples using the preparseCmd() function above.
(The actual response of the commands has been removed -- only the
text displayed by preparseCmd() is shown.)
>look at all
look at A
>examine him; take everything except the box and the book and go north
examine M
take A X the box , the book
go north
preparseCmd() is called even for commands that the parser doesn't
understand. This allows you to rewrite commands that TADS wouldn't
normally understand and put them into a format that's acceptable to
the parser. For example, the preparseCmd() example below will
take sentences of the form "tell <actor> to <command>", and convert
them to the normal TADS syntax, "<actor>, <command>".
#pragma C+
preparseCmd: function(cmd)
{
local i, tot, to_loc, actor, the_rest;
tot = length(cmd);
/* check to see if it starts with "tell" */
if (tot > 3 && cmd[1] == 'tell')
{
/* see if there's a word "to" */
for (i = 1, tot = length(cmd) ; i <= tot ; ++i)
{
if (cmd[i] == 'to')
to_loc = i;
}
/* if there's a "to", convert the command */
if (to_loc != nil && to_loc > 2)
{
/* find the parts before and after the 'to' */
for (i = 2, actor = [] ; i < to_loc ; ++i)
actor += cmd[i];
for (the_rest = [], i = to_loc + 1 ; i <= tot ; ++i)
the_rest += cmd[i];
/* convert it to "actor, command" */
return actor + ',' + the_rest;
}
}
/* otherwise, process the command as normal */
return true;
}
When the parser doesn't know how to handle a sentence, it calls
preparseCmd with the entire rest of the command (which will include
everything before the next THEN or period), and processing is the
same as in any other case. If preparseCmd() returns nil in this
case, the command is cancelled as usual without any further
processing by the parser. If preparseCmd() returns true when the
sentence is not recognized by TADS, the parser will display the
usual message (parseError code 18, "I don't understand that
sentence"). If preparseCmd() returns a list of strings, the
command is replaced with the list, and the parser starts over
processing the new command.
- Several new parseError codes have been added.
In order to produce better messages when actors are involved,
the system now attempts to figure out whether an object in a
command refers to an actor, and if so, to determine whether the
actor should be called "him" or "her". Previously, this
one-size-fits-all message was generated:
What do you want to <verb> it <prep>?
for example:
>hit bill
What do you want to hit it with?
Although it's not always possible to determine which object
should be used in these cases (because disambiguation will not
be possible until the indirect object is known), the system will
make its best guess. To do so, it looks at all of the objects
that might be involved, based on the vocabulary. If the player
appears to mean multiple objects, " them " will be used -- this
is message number 144 for parseError:
>throw ball and bat
What do you want to throw them at? <- message 144
------
If only one object appears to be intended, the parser will try
to figure out whether the object is male, female, or neuter,
using the isHim and isHer properties (these properties are not
new; they have been around since before TADS 2.0). If all of
the objects that match a single noun phrase have isHim and
not isHer, " him " will be used -- this is parseError message
number 145:
>hit bill
What do you want to hit him with? <- message 145
-----
If they all have isHer and not isHim, " her " will be used
(message number 146):
>hit jill
What do you want to hit her with? <- message 146
-----
If both are set, the system equivocates with " them ", message
number 147 (note that this is the same default text as message
144, but it's distinguished as a separate message number in
case a game author wants a more suitable word in this case,
especially in a non-English language):
>hit hermaphrodite
What do you want to hit them with? <- message 147
------
If not all of the objects involved have isHim and/or isHer
set, the system uses "it" (message 141) as in previous versions.
Another change to the processing for this message involves
actors. If an actor is specified in the command, the system
now builds a replacement for message 140. First, message
148 is displayed, which has the default text "What do you want ".
Then, the actor's thedesc is invoked to display the actor's
name. Finally, message 149 (default text " to ") is displayed,
and the rest of the message is built as before.
>guard, throw ball
What do you want the guard to throw it at?
----------------- ----
message 148 149
- The functionality of the parseAskobj function has been extended
so that you can generate the same sort of message that the parser
now generates when a missing object is needed for a command directed
to an actor. To provide the new functionality, the parser now calls
a function called parseAskobjActor. This function is exactly the
same as parseAskobj, except that it takes the actor as the first
parameter, the verb as the second parameter, and the preposition
as an optional third parameter (which is only present when asking
for an indirect object).
If your game defines parseAskobjActor, the system ignores parseAskobj
and calls the new function instead. If parseAskobjActor is not defined,
but parseAskobj is defined, the system calls parseAskobj as it did in
past versions. If neither function is defined, the system generates
the message itself as described above. You should use parseAskobjActor
rather than parseAskobj for new games, since it gives you more
information.
Here's an example of parseAskobjActor, which generates roughly the
same message as the system would (but it doesn't do any of the checking
of isHim and isHer to determine which pronoun to use -- this function
simply uses "it").
parseAskobjActor: function(a, v, ...)
{
if (argcount == 3)
{
"What do you want ";
if (a <> Me) a.thedesc;
" to <<v.sdesc>> it <<getarg(3).sdesc>>?";
}
else
{
"What do you want ";
if (a <> Me) a.thedesc;
" to <<v.sdesc>>?";
}
}
- The parser has been changed slightly to allow a command to specify
an actor in any command within a single line containing multiple
commands. Previously, if an actor was to be specified, the actor
had to be specified at the very beginning of the command. This
restriction has been removed, which allows commands like this:
>joe, north. bob, south. bill, east, take book, west.
Note that, as in past versions, once an actor is specified, the
actor remains in effect for subsequent commands. Since you could
only specify one actor for an entire command line in past versions,
this meant that the actor was used for every command on the line;
with this new version, an actor remains in effect until another
actor is specified. So, in the command above, "north" is directed
to Joe, "south" is directed to Bob, and "east, take book, west"
is directed to Bill.
- The maximum number of notifiers has been increased to 200. The
maximum number of daemons and fuses has been increased to 100 each.
- The maximum number of ambiguous words matching a particular
vocabulary word has been increased to 200. This should relieve
problems that some people have reported with the error message
"The word 'foo' refers to too many objects".
- The compiler now supports most of the remaining C operators:
a % b - returns the remainder of dividing a by b
a %= b - sets a to a % b
a != b - equivalent to a <> b
!a - equivalent to (not a)
a & b - bitwise AND
a &= b - sets a to the bitwise AND of a and b
a | b - bitwise OR
a |= b - sets a to the bitwise OR of a and b
a && b - equivalent to (a and b)
a || b - equivalent to (a or b)
a ^ b - bitwise XOR of a and b
a ^= b - sets a to the bitwise XOR of a and b
~a - bitwise negation of a
a << b - a shifted left by b bits
a <<= b - shifts a left by b bits
a >> b - a shifted right by b bits
a >>= b - shifts a right by b bits
Note a slight complication involving the >> operator: you can't
use this operator from within an embedded string expression, because
it would be confused by the parser for the end of the expression.
It doesn't help to use parentheses, since the embedded string
processing is essentially a textual substitution mechanism which
happens without knowledge of the expression context (and is thus
unaware of parenthesization). For example, this would be illegal:
myprop = "x divided by 128 is << (x >> 7) >>!" // wrong
You would have to code this instead as:
myprop = { "x divided by 128 is "; x >> 7; "!"; } // right
Another slight complication arises from the use of the & operator
in lists. Since TADS allows list elements to appear without any
separating punctuation (except whitespace), you can have a list
that looks like this:
mylist = [&sdesc &adesc &thedesc]
This construct is still legal, and is still interpreted with the
"&" operators as unary operators, not bitwise AND operators.
However, the parser now warns when such a definition is used; see
the description of the new warning TADS-357 below for details.
- The compiler will generate a new warning when it detects a unary
operator within a list that could also be interpreted as a binary
operator. For example, in this list,
list2 = [5 -2 -6 -7]
the "-" operators could be interpreted either as unary negation
operators, which would result in a list with four elements (the
numbers 5, -2, -6, and -7), or as binary subtraction operators,
which would result in a list with only one element (the number
-10). This same problem arises with the operators "+" and "&",
since these also have a unary and binary interpretation which
depends on context.
In these cases, the compiler interprets the operators as unary
operators, and issues a warning to let you know that the usage
was ambiguous. The warning is TADS-357:
TADS-357: warning: operator '-' intepreted as unary in list
Note that this is a change from the previous version for the '+'
and '-' operators. If your game depends on building lists from
calculated numeric constants, you will need to change your code;
we don't expect that any games actually depend on the old behavior.
If you really do want the operators in these cases interpreted
as binary operators, use parentheses:
list2 = [(5 -2 -6 -7)]
The parentheses tell the compiler that the expression is to be
interpreted as a single list element.
If you want the unary interpretation, and you want to suppress
the warning, use commas between the list elements:
list2 = [5, -2, -6, -7]
This doesn't change the interpretation, but it does suppress the
warning, because it removes the ambiguity: when the commas are
present, there is no way the '-' operators could be interpreted
as binary operators.
You can suppress this warning using a new check box in the
compiler's warning level dialog. Check the box to allow TADS-357
errors to be displayed. You may want to suppress this warning when
compiling code written prior to this version of TADS, since the
warning is generated in case you wanted the new meaning of the &
operator. The check box enabling the warning is checked by
default; uncheck it to suppress the warning.
- A new compiler command-line option has been added: -C, a toggle
option, which turns on and off C-language operator compilation.
By default, C operator mode is off (-C-), which makes the compiler
use the normal TADS operators. Specifying -C+ turns on C operator
mode; specifying -C- disables C operator mode.
When C-language operator mode is in effect, two operators are
affected: the assignment operator becomes '=', and the equality
comparison operator becomes '=='. With normal TADS operator mode
in effect, assignment is ':=' and equality is '='. If you are
a C programmer, and you're unhappy with the slight variation in
operator notation between TADS and C, you can use -C+ to make TADS
behave more like a real language. Thanks to the #pragma C options
(see below), C operator mode has no effect on your choice of
header files -- you can use the same old adv.t and other header
files unchanged, and still use C-style operators in your code.
- A new preprocessor command has been added: #pragma. This special
directive can be used to specify certain compiler options from
within your source code. Currently, the only #pragma option available
is the C operator mode option. Use #pragma C+ to turn on C operator
mode, and #pragma C- to turn it off.
The #pragma C+ and #pragma C- settings are local to a particular
file. If a file is included by another file, the #pragma C settings
specified in the included file will be in effect only until the end
of the included file; the including file's #pragma C settings that
were in effect before including the other file will be restored at
the end of the included file. adv.t and std.t now start with a
#pragma C- command -- this allows adv.t and std.t to be included
from a file with C-style operators (and thus a #pragma C+ or
command-line C operator mode setting). Since the enclosing file's
#pragma C option will be restored after the inclusion, files with
different operator modes can be freely intermixed with #include, as
long as each included file specifies its desired mode with a
#pragma C directive.
- The precedence of the comparison operators has been changed to
be the same as that used by C. Previously, all of the comparison
operators were at the same precedence; starting with this version,
== and <> (and thus !=) are at the same level of precedence, and
associate left to right as before, but <, >, <=, and >= are one
level higher in precedence. The following type of expression will
be affected:
a > 1 <> b > 1
Previously, this grouped as:
(((a > 1) <> b) > 1 // obsolete
This now groups as:
(a > 1) <> (b > 1) // current behavior
This shouldn't affect any existing code, since the old interpretation
should always have resulted in an error (because a truth value, true
or nil, can not be compared in magnitude to a number or other type).
- Limited conditional compilation and preprocessor text substitution
(#define) support has been added to the compiler. The following
preprocessor directives are now available:
#define symbol value
#undef symbol
#ifdef symbol
#ifndef symbol
#else
#endif
#define is used to assign a value to a preprocessor symbol. The
"value" is simply text that will be substituted verbatim for the
symbol whenever it occurs in your file (other than within quoted
strings). For example:
#define TEST say('hello from TEST!')
myfunc: function
{
TEST;
}
The symbol TEST is replaced with its definition, so the function
myfunc() displays "hello from TEST!" when called.
Preprocessor symbols defined with #define are in a separate
namespace from all other symbols in your program. Unlike a
standard C preprocessor, no arguments are allowed in #define
macros.
#undef deletes a previously #define'd symbol. You can #undef
the special symbols defined automatically by the compiler if
you wish (see below).
#ifdef tests to see if a preprocessor symbol is defined. If it
is, the lines following the #ifdef line, and up to the corresponding
#else or #endif, are included; otherwise, they are ignored.
#ifndef is the opposite of #ifdef: #ifndef tests to see if the
symbol is NOT defined. If the symbol is undefined, the lines
following the #ifndef line up to the corresponding #else or
#endif are included; otherwise, they are ignored.
#else indicates that the lines between the #else and #endif are
to be included if an only if the corresponding #ifdef (or #ifndef)
failed. #else is optional. At most one #else is allowed per
conditional.
#endif terminates a conditional block. Exactly one #endif must appear
for each conditional (#ifdef or #ifndef).
You can use #ifdef to compile certain parts of your code
conditionally. For example, if you want to include a verb only
for your debugging version of a game, but you want to remove it
from the final version, you could do something like this:
#ifdef __DEBUG
magicVerb: deepverb
verb = 'xyzzy'
action(actor) =
{
}
;
#endif
Note that __DEBUG is especially handy for this sort of thing,
because the compiler automatically defines this symbol when
debugging (-ds) is turned no (see below).
- The compiler automatically defines several preprocessor symbols.
These symbols can be used or tested within your code as needed.
__TADS_VERSION_MAJOR is defined to a number indicating the major
version number of the compiler (in the present system, 2).
__TADS_VERSION_MINOR is defined to a number indicating the minor
version number (in the present system, 2).
__TADS_SYSTEM is defined to a string (single-quoted) identifying
the operating system the compiler is running on. In addition, the
same identifier contained in the string is defined as a preprocessor
symbol itself (its value is always 1; it is intended that it will
be tested with #ifdef, and not otherwise used). For example, if
__TADS_SYSTEM is 'MSDOS', the symbol MSDOS will be defined to 1.
If __TADS_SYSTEM is 'Macintosh', the symbol Macintosh will be
defined to 1.
__DEBUG is defined to 1 if debugging is turned on for this
compilation (with the -ds compiler option). Otherwise, this symbol
is not automatically defined. You can test the existence of __DEBUG
with #ifdef to conditionally include code only when you are compiling
for debugging. This might be useful if you want to include certain
commands only in the debugging version of your game, and want to
remove them when you actually deliver the game to players.
If this set of symbols were entered manually with #define statements,
the definitions might look like this:
#define __TADS_VERSION_MAJOR 2
#define __TADS_VERSION_MINOR 2
#define __TADS_SYSTEM 'MSDOS'
#define MSDOS
__DATE__ is defined as a single-quoted string giving the system date
when the compilation began, in the format "Jan 01 1980".
__TIME__ is defined as a single-quoted string giving the system time
when the compilation began, in a 24-hour format, "13:40:50".
__FILE__ is defined as a single-quoted string giving the file being
scanned at the point where the __FILE__ macro is encountered. Each
time you use __FILE__, it will have the correct value for that point
in your source code.
__LINE__ is defined as a number giving the line number at the point
where the __LINE__ macro is encountered. Each time you use __LINE__,
it will have the correct value for that point in your source code.
- The compiler has two new options that give you further control
over preprocessor symbols. The -D option allows you to define
a preprocessor symbol from the command line. For example, to
define TEST to 5 from the command line, you could do this:
tc -i/tads/include -DTEST=5 mygame.t
Note that if you omit the equals sign, the default definition
of the symbol will be 1:
tc -i/tads/include -DTEST mygame.t
This defines TEST to 1.
The -U option undefines a predefined symbol. You can use this to
undefine one of the symbols automatically defined by the compiler.
You can also use it to undefine a symbol defined in a precompiled
header, if you're loading one; -U is applied after the precompiled
header is loaded, so it will undefine symbols loaded from the file.
For example, to compile for debugging, but leave __DEBUG undefined,
you could do this:
tc -ds -U__DEBUG mygame.t
The -U option is applied after all -D options, so you can also use
it to undefine a symbol placed earlier on the command line. This
may be useful if you are using a configuation file (CONFIG.TC)
that contains -D options for symbols you sometimes want to undefine.
- The compiler has a new preprocessor directive, #error, which allows
you to generate your own error during compilation. If a #error
directive is encountered, any text after the #error is displayed
as a compiler error; an occurrence of #error is counted as an actual
compilation error, so compilation will fail if #error is encountered.
For example:
#ifndef TEST
# error TEST is not defined!
#endif
If the preprocessor symbol TEST is not defined at the point when
this sequence is encountered, the compiler will display an error:
mygame.t(181): error TADS-124: TEST is not defined!
- The runtime has a new debugging feature that may help you track
down problems with word definitions. You can make the player
command parser generate a number of status messages as it analyzes
a player's command; these messages provide information on how the
parser is interpreting the words in the command.
To activate this new debug mode, use the debugTrace function with
these arguments:
debugTrace(1, true);
To turn the debug mode off, call with nil instead of true. This
debugTrace function is always available, even when running under
the normal runtime; the function returns no value when called with
these arguments.
- The built-in function incturn() has been extended to allow you
to run a series of turns all at once. You can now specify a numeric
argument to incturn(); the argument gives the number of turns that
should pass. An argument of 1 causes incturn() to behave as usual.
When an argument higher than 1 is given to incturn(), the function
runs all of the fuses that are set to burn down within the number
of turns specified, but not after that number of turns. Note that
the normal incturn() has never actually executed any fuses, but
simply burns down all fuses by one more turn.
For example, if you call incturn(2), the system will first run
any fuses that are set to burn down after 1 turn, then will shorten
all remaining fuses by one more turn.
- A new built-in function, skipturn(), has been added. This new
function takes a numeric argument specifying the number of turns
to skip; it must be at least 1. skipturn(n) is similar to incturn(n),
except that it does not run any of the fuses that burn down during
the 'n' turns -- instead, it simply removes them without running
them.
- A new built-in function allows you to force capitalization off --
this function, nocaps(), is the opposite of caps(). If you call
caps() then call nocaps(), the next character is lower-case; if
you call nocaps() then caps(), the next character is capitalized.
Along with nocaps(), the special sequence \v has been added. Using
this sequence in a displayed string is equivalent to calling
nocaps(); this sequence is analogous to \^, which is equivalent
to calling caps().
- The parser is now capable of disambiguating direct objects before
indirect objects. By default, everything works as it always has --
in a two-object command, the indirect object is disambiguated first,
and then the direct object is disambiguated in the presence of the
known indirect object.
It is now possible, however, to specify that the reverse should be
done. To do this, you use a new special flags syntax when defining
your verb:
tellVerb: deepverb
verb = 'tell'
desc = "tell"
ioAction(aboutPrep) = [disambigDobjFirst] 'TellAbout'
;
The new special flags are placed in square brackets between the
equals sign and the property template for the verb definition.
The flags currently accepted are:
disambigDobjFirst
disambigIobjFirst
Note that disambigIobjFirst is provided for completeness only; it
is never needed, because it is the default setting.
When the disambigDobjFirst flag is specified, it means that the
command should have its direct object disambiguated before its
indirect object.
When the disambiguation order is reversed, the normal argument
lists for verDoTellAbout and verIoTellAbout are interchanged.
For our example, the prototypes for the verifier methods become:
verDoTellAbout(actor)
verIoTellAbout(actor, dobj)
Normally, verIoVerb would not receive the direct object as an
argument, because the direct object would not be known at the time
of the verIoVerb call; and verDoVerb would receive the indirect
object as an argument, because it would be known by the time the
direct object was being tested. When the disambiguation order is
reversed, however, so are the prototypes to these functions.
The actual action method, ioTellAbout(actor, dobj), remains
unchanged. All other methods also remain the same.
When the direct object is disambiguated first, the player is not
allowed to use multiple direct objects (or multiple indirect objects)
in the command. If the player tries to do so, the new parseError
message number 28 is displayed:
>tell bob and bill about gun
You can't use multiple objects with this command.
- Note that a .GAM file format change was required to support the
extra information needed for the disambigDobjFirst flag. The TADS
file format is now format "C". The compiler is still capable of
producing formats A or B, in case you need to generate .GAM files
that can be played with versions prior to 2.2; however, if you
use a format prior to C, you will not be allowed to use the
disambigDobjFirst flag (or any other similar flags that may be
added in the future).
- Two new methods have been added to disambiguate actors. Previously,
the system validated and disambiguated an actor by pretending that
you were attempting to take the actor -- takeVerb.validDo and the
actor's verDoTake were used to validate and disambiguate the actor,
when all you wanted to do was speak to him. This did not always
produce satisfactory results, and in particular did not allow for
such situations as talking over a radio to an actor in another room.
To provide better control over actor validation and disambiguation,
the system for testing actors has been enhanced.
First, to validate an actor, the system now uses the method
validActor in the actor object itself. This method takes no
arguments; it returns true if the object is valid as an actor in
a command, nil otherwise. This method is called before the verb
or any of the objects involved in the sentence are known. Its
function is not to determine whether the actor wants to receive
the command, or even if the object can be used as an actor
(actorAction is the place to do both of these tests), but rather
simply to determine if the object can be addressed by the player
at all. This method should return true if the actor is accessible
by voice command (or whatever other means you want to provide for
giving commands) to the player. The default thing.validActor in
adv.t returns true if the object is reachable by Me, which provides
roughly the same behavior as the old takeVerb-based mechanism.
Second, to disambiguate an actor, the new preferredActor method
has been added. This method is called if an actor is ambiguous.
As with validActor, it doesn't take any arguments, and it returns
true if the object is "preferred" as an actor, nil otherwise. If
exactly one ambiguous object's preferredActor method returns true,
the parser will use that object as the actor without further
questioning the player; otherwise, the system will ask the player
to disambiguate the noun as normal. In adv.t, movableActor defines
preferredActor = true.
An example of how these might be used:
Sleeping Compartment
You are in a sleeping compartment on a moving train.
A pair of bunks is along each wall.
There is a copper wire here.
The train conductor is standing in the doorway, asking for
your ticket.
>look at conductor
Which conductor do you mean, the copper wire, or the train
conductor?
>copper
It's a piece of wire, about a foot long.
>conductor, where is ivan?
"Your ticket, please," is all the conductor has to say.
In this sequence, when the command is addressed to "conductor", the
parser matches both the copper wire and the train conductor. It
checks validActor in each of them -- both return true, since both
are accessible to the player. (As described above, even though the
copper wire couldn't possibly be an actor, it is valid as an actor
at this point -- it's not until its actorAction that we will decide
that there's no point in talking to it.)
So, the parser has an ambiguous actor. The parser tries to
disambiguate the actor by testing preferredActor in each object.
The copper wire's preferredActor returns nil; the train conductor's,
however, returns true, because the conductor is a movableActor
object. The parser now has only one object, and thus doesn't
need to ask the player for further information.
For compatibility with games compiled with previous versions of
adv.t, the parser will continue to use the old mechanism (involving
the takeVerb) if validActor is not defined in your game. Each time
an actor is used, the parser checks to see if the first object in
the list of possible actor objects has a validActor method defined;
if not, the parser uses the old mechanism. If you use the new adv.t,
all objects will at least inherit a validActor method (from thing),
so testing for the presence of this method in any object lets the
parser determine if the new mechanism can be used with the game.
- A new parseError message has been added: number 27, whose default
text is "You can't repeat that command." This message is displayed
when the player types AGAIN, but the parser can't repeat the command;
this is the case if one of the objects involved in the command is
no longer accessible.
- A new parseError message has been added, related to validActor
(see above). This new message, number 31, is used when none of
the objects matching the vocabulary for an actor in a player's
command can be used as an actor. The default text of this message
is "You can't talk to that." For example, if there is no object
matching the vocabulary "guard" whose validActor method returns
true, but an object named "guard" is visible to the player, the
message is used:
Security Room
You are inside a small cubicle. A thick, laser-proof
(as you now know from your ill-fated attempt) glass door
(closed) is to the north. Through the door you can see
a guard standing watch.
>guard, open door
You can't talk to that.
This message is used for actors in place of the standard cantReach
processing done for direct and indirect objects under these conditions.
- A new parseError code, number 29, has been added. The default
message is "I think you left something out after 'any of'", and
is used when the player uses "any of" in a sentence, but doesn't
follow it with anything.
- Using the same word as both a plural and a noun works much better
now. The parser will first attempt to use such a word as a plural;
if no objects match the plural usage, the parser will try to use
the word as a noun instead.
- "of" can now be used as a preposition. In previous versions of
the run-time, "of" was exclusively a special word that was embedded
in noun phrases (such as "pile of paper"). The runtime will still
allow "of" to be used in noun phrases as before, but it also will
treat "of" as an ordinary word when, based on the context, it does
not appear to be part of a noun phrase.
The parser will still attempt to treat "of" as part of a noun
phrase whenever it matches an object. For example, if you have
an object with nouns matching "pile of paper", then the following
interpretations will apply:
accuse bob of murder -> dobj = bob, iobj = murder, prep = of
accuse pile of paper -> dobj = pile of paper
Note that ofPrep, defining "of" as a preposition, has been added
to adv.t.
- All of the system verbs that use "abort" have been modified
slightly in adv.t to make it easier to augment their behavior
with the 'modify' statement. All of the processing other than
the "abort" has been moved out of the doVerb (or action) method
in each case, and put into a new method. For example, saveVerb's
action routine now looks like this:
action( actor ) =
{
self.saveGame(actor);
abort;
}
The new method saveVerb.saveGame(actor) now performs all of the
processing that the action(actor) method previously performed.
The benefit of this change is that you can now modify the
saveGame(actor) method, and inherit the original behavior,
without having to worry about an "abort" interfering with the
order of operations. For example:
modify restoreVerb
restoreGame(actor) =
{
// restore the game as usual - check for success
if (inherited.restoreGame(actor))
{
// re-randomize the puzzle
"The carnival hawker flashes a mischevious
smile at you. \"There's no use trying to
guess the answer,\" he says. \"I changed
around the shells while you were busy
restoring!\"";
puzzle.answer := rand(100);
}
}
;
- The format mask fmtMe has been added. You can now use %me% in
messages to refer to the actor. For basicMe, fmtMe is set to
the message "me"; for other actors, it is set to the actor's
thedesc. In adv.t, thing.ldesc has been changed to use %me%:
"It looks like an ordinary <<sdesc>> to %me%." This makes the
default sentence somewhat more adaptable if you ask another
actor to describe something:
>guard, look at the card
It looks like an ordinary card to the guard.
- AGAIN, WAIT, and SLEEP are now darkVerb's in adv.t.
- A new verb, breakVerb, has been added to adv.t. The vocabulary
words for this verb are 'break', 'destroy', and 'ruin', and it
provides a single-object command "break <direct-object>". The
thing class has been adjusted so that verDoBreak(actor) validates
breaking any object, but doBreak(actor) simply displays "You'll have
to tell me how to do that." If you want to make a breakable object,
simply override doBreak(actor) so that it breaks the object (you may
also want to override verDoBreak(actor) so that it doesn't allow a
broken object to be broken again).
- "there" has been added a synonym for "it" in the specialWords list
in adv.t. This allows sentences like this:
>get box. put ball in there.
- lightsource now has a doTurnnon method in adv.t. This method will
show the room's description if the room becomes lit as a result
of turning on the lightsource. Note that lightsource, by default,
has no verDoTurnon, so you can't turn a generic light source on
and off. However, if you add switchItem to the superclass list of
a lightsource object, you will have a light source that you can
turn on and off, and which will have this new behavior. Note that
you should put lightsource in the superclass list prior to
switchItem, so that lightsource.doTurnon overrides switchItem.doTurnon:
flashlight: lightsource, switchItem
sdesc = "flashlight"
noun = 'flashlight' 'light'
adjective = 'flash'
location = tunnel
;
- verIoGiveTo and ioGiveTo have been added to movableActor in adv.t.
verIoGiveTo rejects the command if the actor is the same as the
indirect object, otherwise the command is accepted. ioGiveTo
always rejects the offer. In addition, ioGiveTo has been added
to basicMe; the method always accepts anything from another actor,
because this method will only be called in response to commands
such as:
>guard, give me the key
- A small problem with thing.isVisible in adv.t has been fixed. If
the vantage is inside the object, and the object's contents are
visible, isVisible returns true. This is is needed in certain
situations involving nested rooms.
- moveableActor.travelTo(room) in adv.t has been corrected so
that it does nothing when room = nil, which is the case when the
actor can't travel in the desired direction. Previously, the
"noexit" message would be displayed, but then the actor would
be moved into a nil location.
- moveableActor now has a roomCheck method in adv.t. The method
is the same as basicMe's roomCheck method; its omission in
previous versions was an oversight.
- The compiler now sets an error code on exit. If you are using
a MAKE utility or other program-building tool, you can use the
exit code. On success, the compiler uses exit code 0; if any
errors occurred, a non-zero exit code is used.
- The compiler no longer creates a .GAM file if errors occurred
during compilation. If a .GAM file of the same name exists
prior to a compilation, and errors occur, the original .GAM
file is unaffected (it is not deleted or overwritten). Although
the compiler previously produced a .GAM file even when an error
occurred, this .GAM file was not generally usable; to avoid
confusion, the compiler no longer produces a .GAM file at all
when an error occurs.
- The compiler performs more checking for invalid memory options.
When memory options are entered that exceed the compiler's
internal limits, the compiler will report an error (after the
Compile button is pushed) and halt the compilation. The affected
settings are the parse pool size, local symbol table size, and
the stack size. The runtime similarly checks its parameters
more carefully now.
- $$ABEND can now be used after a question from the parser, such
as during disambiguation and when OOPS is permitted.
- The runtime no longer displays anything on the status line at
startup. It initializes both the left portion (which normally is
used to display the location) and the right portion (which is
normally used to display the score and turn count) to empty
strings. To ensure that a score is properly displayed at the
start of the first turn, we added a call to scoreStatus(0, 0) in
the "init" function defined in std.t.
- The runtime handles hyphenation better. Multiple hyphens will
no longer be split across lines -- so if you use two hyphens
together for a dash, both hyphens will always be grouped on one
line. Furthermore, the runtime formatter will never put a dash
at the very start of a line; if a line must be split at a dash,
the formatter will put the dash at the end of the line, then
break the line, and will back up and split at the previous word
if necessary.
- The runtime will now properly convert "\" sequences in askfile()
prompts properly. If you use a \n, \t, \', \", or \\ sequence
in a prompt to askfile(), the sequence will be displayed as the
appropriate character.
- The Mac runtime's startup dialog has an additional option that
lets a user show all files, rather than just files that the
runtime recognizes as TADS game files. This should make it
easier to run a game transferred from another system -- since
you can now run a game regardless of its type/creator settings,
you can simply transfer a file from another operating system and
run it directly. To show all files, click the radio button
labelled "Show All Files" at the bottom of the file dialog;
to return to showing only TADS game files, click the radio
button labelled "Show TADS Games Only."
- The TADS Executable Game Builder now lets you specify run-time
command options that should be used when the game is executed.
This new feature, which is optional, lets you specify a set of
run-time options that you want to be in effect every time your
game is run.
To specify command options for your game executable, you must
first create a file. Use the same format as CONFIG.TR -- simply
enter your options into the file as you would on a TR command
line; separate options by newlines or spaces. For example, to
specify a minimal cache size and a swap file of SWAP.DAT, you
could make your CONFIG.TR file look like this:
-m0 -tf swap.dat
See the Author's Manual chapter on the run-time command-line
options. Even though the Macintosh version of the run-time
doesn't normally use a command line at all, you must still enter
options into a file using the command line option format if you
want to combine them with your game.
Once you've created a file with your command options, specify
that file to the Executable Game Builder by selecting it with
the new file selector dialog that appears after you specify the
game file. If you don't want to combine an options file with
your game, simply cancel this dialog.
Once the config file is bound into your executable, its options
will be used every time a player runs the game. Note that you may
want to avoid specifying anything specific to your system, such as
volume or folder names, since that may prevent the game from working
properly on someone else's computer.
- The Macintosh compiler's "memory options" dialog box has been
corrected so that it accepts settings over 32767. Memory settings
up to 65535 can now be entered.
- The Macintosh compiler no longer displays "Compilation Completed"
when compilation fails or is interrupted. Instead, it displays
the new message "Compilation Terminated", to make it clear that
the compilation did not complete normally, whenever errors occur
during a compile, or the compilation is cancelled by Command-Period.
- The Macintosh compiler has been corrected so that it doesn't add
".t" to the end of source file names any more. In previous versions,
the compiler always added ".t" to the end of a filename if the
filename didn't contain at least one period; while this behavior
is desirable on other computers (since it allows command-line
users to omit the default filename extension, saving typing), it's
unnecessary and incorrect on the Macintosh.
- The Macintosh run-time's formatting system has been improved so
that it will no longer insert spaces after hyphens. Previous
versions occasionally inserted a space after a hyphen, even when
the original text had no spaces after the hyphen.
- The new sentence parsing for the form VERB PREP IOBJ DOBJ
(introduced in 2.1.1) causes a subtle problem: if you create
an object with an adjective that is also a preposition, the
system attempts to interpret sentences with a single-word verb
that refers to the object as VERB PREP IOBJ. The result is
this:
>push off button
What do you want to push?
This is because the parser is interpreting the sentence as
"push something off button", and needs to know the "something".
This problem has been corrected. Now, the parser will check this
type of case to see if the preposition can also be used as an
adjective, and if so, checks to see if a noun (possibly preceded
by one or more adjectives) follows; if this test is met, the word
will be interpreted as an adjective, as it should be.
- The parser now correctly distinguishes between cases involving
a word that is defined as both a preposition and an adjective.
The parser previously did not accept sentences such as this:
>enter south wall
when 'south' was defined as an adjective. Because 'south' was
also defined as a preposition (in adv.t), the parser attempted
to interpret this sentence as though 'enter south' were a verb
(in the same manner as 'pick up' or 'put down'). The parser
now checks to make sure that 'enter south' is a valid combination
verb; if it's not, and 'south' is also defined as another part of
speech, the parser assumes that 'enter' is the verb, and treats
'south' as an adjective.
- The parser previously asked to disambiguate a direct object twice
if askio(prep) was used. For example:
>unlock door
Which door do you mean, the large door, or the small door?
>large
What do you want to unlock it with?
>key
Which key do you mean, the silver key, or the gold key?
>gold
Which door do you mean, the large door, or the small door?
>large
The door unlocks with a satisfying click.
This has been corrected -- the parser now only asks once about
the door.
- The parser did not previously accept a word during disambiguation
if the word was defined as both a noun and an adjective. For
example, if you've defined objects "violet paper", "violet banana",
and "paper towel", and the parser asked you this:
>x violet
Which violet do you mean, the violet paper, or the violet banana?
then you couldn't respond with "paper". This has been corrected.
- The inputkey() function now clears the "more" line counter. So,
when inputkey() is called, a "more" prompt will not show up until
another screenful of text has been displayed.
- "local" statements that occur out of context (i.e., as other than
the first statements after an open brace) now generate better
diagnostics.
- Dividing by zero is now flagged as a run-time error.
- A problem involving the embedded string << >> notation has been
corrected. Under certain circumstances, if one expression used
in an embedded string invoked another string with an embedded
expression, an error occurred (usually "invalid type for built-in
function"). This should no longer occur.
- The system did not properly handle lists containing function
pointers. This has been corrected.
- A problem involving pre-compiled headers and 'modify' has been
corrected. Several people have encountered problems that generally
were manifested as "assert" failures in mcm.c (the cache manager)
when using 'modify' and pre-compiled headers; these should no
longer occur.
- An internal cache corruption occurred under certain obscure
circumstances involving 'modify'. This has been corrected.
- A note on roomCheck: if you return nil from roomCheck, fuses
and daemons are NOT run; in this sense, returning nil from roomCheck
is equivalent to using abort in other methods. (This is not a
change -- it's always worked this way -- but this behavior is
not mentioned in the TADS Author's Manual.)
- A note on using << >> embedded strings: If you put a newline
immediately before the << of an embedded string, any spaces between
the last non-space character preceding the << and the << will be
lost. If you want a space before the <<, do not put it on a new
line. Instead, you can put it at the end of the line, and put
the embedded expression itself on the next line. For example:
embeddedString = "embedded string"
sdesc = "This is a message with an
<<self.embeddedString>>!" // wrong
This will display as follows:
This is a message with anembedded string!
If you intend a space to precede the embedded string, you should
write this as follows:
sdesc = "This is a message with an <<
self.embeddedString>>!"; // right
(This is not a change, but simply a note for game developers who
have encountered this problem.)
2.1.2 11/22/93 enhancements, bug fixes
- You can now detect when the player uses multiple direct objects
with a verb, and reject such commands. Whenever the player uses
multiple direct objects with a command (or uses "all", even if it
results in a single object being used), the parser calls the
verb object's rejectMultiDobj(prep) method. If you don't wish to
take any special action for multiple direct objects used with a
particular verb, simply return nil from this method (or don't
define the method at all for the verb). If you want to prevent
multiple direct objects from being used, however, you should display
an appropriate message, and return true. The parser will skip the
command entirely. Note that the parser doesn't display any additional
message when rejectMultiDobj(prep) returns true; the method should
display whatever message is desired. The "prep" parameter is the
preposition object used with the command; it will be nil if no
indirect object is present. An example:
modify inspectVerb
rejectMultiDobj(prep) =
{
"You can only look at one thing at a time.";
return true;
}
The verb's rejectMultiDobj(prep) method is called immediately
before the actor's actorAction method. Note that the parser will
continue processing any remaining commands on the line, and will
then run daemons and fuses as normal, even if rejectMultiDobj(prep)
returns true; if you want to stop the current turn altogether, use
abort.
- The player command parser now gives you greater control over object
validation error reporting. In previous versions, if an object was
visible but did not pass the validIo/validDo test, the parser called
the object's cantReach method to report the error (see the note below
about an additional change to object.cantReach processing).
Now, however, the parser will call verb.cantReach instead, if the
command's deepverb object defines (or inherits) a cantReach method.
If the verb does not have a cantReach method at all, the parser
will use the old behavior instead. The new cantReach method should
be defined as follows:
myVerb: deepverb
verb = 'whatever'
cantReach(actor, dolist, iolist, prep) =
{
// your code here
}
;
This method doesn't return a value; it simply displays the appropriate
message explaining why the object can't be used with the command.
verb.cantReach is used only when the objects are visible (that is,
object.isVisible(actor) returned true for each object in the list).
Only one of dolist or iolist will be non-nil. If the direct object
of the command refers to one or more objects that are visible but
can't be used (according to validDo), dolist will be a list of all
such objects, and iolist will be nil. Otherwise, iolist will be a
list of such objects used for the indirect object, and dolist will
be nil.
adv.t has not been changed to use verb.cantReach. This change has
been made to provide finer control for game authors implementing
their own verbs and object validation procedures.
- The player command parser had an odd quirk when ambiguous nouns
were used with transparent items. If the player used a command
containing a noun that referred to multiple objects that were
visible but were not valid for the verb (for example: "take trophy"
in a room containing a closed glass trophy case containing a bowling
trophy and a tennis trophy), the parser asked the normal disambiguation
question. This was not really necessary, because the parser already
knew that the objects were invalid. This has been changed; the parser
now simply uses the cantReach message for *each* object that is
visible and matches the vocabulary, using the usual multiple-word
format:
bowling trophy: You'll have to open the glass case first.
tennis trophy: You'll have to open the glass case first.
This new behavior should have no effect on your game code. Note
that it is entirely irrelevant if you use the new verb.cantReach
feature described above.
- The compiler sports a new case sensitivity option. By default,
the compiler is case-sensitive, as it has been in past versions.
However, the new toggle option allows you to change this. Specify
-case- to turn off case sensitivity (the default is -case+). Note
that this is a toggle option, so simply using -case will reverse
the current case sensitivity (which is useful if you use a CONFIG.TC
file that sets a non-default case option). When case sensitivity
is turned off, the compiler will treat upper- and lower-case letters
in symbols (names of objects, properties, functions, and local
variables) as equivalent. Hence, foodItem = fooditem = FoodItem,
and so on.
If you create a pre-compiled header with -w, any compilation which
reads that binary header with -l will use the same case sensitivity
as was in effect when the header was pre-compiled. The -case option
is ignored when a pre-compiled header is loaded. Likewise, the
debugger uses the same case sensitivity that was in effect when the
game being debugged was compiled.
- The debugger's command set is no longer case-sensitive (hence,
BP = Bp = bP = bp).
- adv.t has a new darkVerb class. This is a type of deepVerb that
can be used in the dark. The darkroom class has been changed to
accept any verb of class darkVerb in its roomAction and roomCheck
methods. The travel verbs and system verbs have all been made
darkVerb objects, so darkroom only needs to check the single
verb type.
In a related change, turnOnVerb and dropVerb have been changed to
be darkVerb objects, allowing the player to turn on an object or
drop it in the dark. The verDoTurnon method in switchItem has
been changed so that it checks to see if the player is in a dark
room; if so, the item can only be turned on if the player is already
carrying the object. This allows the player to turn on a light
source that's already being carried, but doesn't allow the player
to pick up a light source in a dark room.
- The compiler issues a new warning message (TADS-452) if you
use the same verb with two deepverb objects. The parser can only
choose a single deepverb object for any verb typed by the player,
so you should never define the same 'verb' vocabulary word in more
than one deepverb object. In past versions, the compiler did not
flag this as a warning.
- Several improvements have been made for numbers in player
commands. First, multiple numbers are now allowed in a single
commands; for example, the player can now say "press 1, 2, 3,
4 on keypad," and the numbers are set in numObj.value, one by
one. Second, the sdesc, adesc, and thedesc properties of
basicNumObj have been improved to show the number's value.
- Similar improvements to those for numbers have been made for
strings. Multiple strings are now allowed in a single command,
and the basicStrObj properties sdesc, adesc, and thedesc have
been improved to show the string's value.
- specialWords has been enhanced. First, in past versions, if a
game contained multiple specialWords statements, the word lists
were additive -- all specialWords lists were used in the game.
This has been changed so that each specialWords statement replaces
any previous list in effect. However, you now can explicitly add
to the specialWords list, without removing any of the previously
defined words, by using "modify specialWords". When you use
'modify', you can use nil in any word slot if you do not wish
to add any words for that slot. Finally, you can use "replace
specialWords" to make the replacement explicit; this is the default
if neither 'modify' nor 'replace' is specified, but the compiler
will now issue a warning (which is harmless) if you use specialWords
without 'replace' or 'modify' and a previous specialWords list is
already in effect.
- The words "one" and "ones" (or their equivalent for your game,
if you've changed them with specialWords) are no longer considered
reserved words within normal commands. This allows you to use
objects such as a "one dollar bill"; previous versions rejected
player commands containing "one" or "ones". These words are now
considered special only during the parsing of a response to a
disambiguation question, when they can be used in place of a
noun ("the red one" can be used in answer to "Which book do you
mean...").
- The hider class has been changed so that 'it' or 'them' (as
appropriate) are set to the object or objects found when
searching the hider.
- The verDoPutIn and verDoPutOn messages in thing and surface
(respectively) have been improved for the somewhat obscure case
of attempted circular containment - that is, putting an object
into a container, when the container is already in the first object
(either directly or by virtue of being inside another object which
is inside the first object, or inside an object which is inside an
object which is inside the first object, and so on). The new
method thing.circularMessage(iobj) is called in these cases to
display an appropriate message; the default implementation of this
method displays the complete list of containers of the direct
object out to the indirect object. For example, if you have a
crate which contains a box, and you try to "put crate in box",
the message is "You can't put the crate in the box, because
the box is already in the crate."
- The default doTake method has been changed to include the weight
of any contents of the item being taken, in addition to the item
itself, to determine if the actor's inventory is too heavy.
The old doTake method only included the weight of the object
being taken, not counting its contents.
- The Actor class has been changed to add a travelTo method. You
can now move any actor (Me included) with travelTo(destination).
The default Actor.travelTo method moves the actor; it announces
the departure of the actor if the actor was in the same location as
Me before leaving (and the location is lit); and it announces the
arrival of the actor if the actor is moving into the same location
as the player (and the location is lit). The departure message
is generated with a call to self.sayLeaving, and the arrival
message is generated with self.sayArriving. The default versions
of these methods simply display "Thedesc leaves the area" and
"Thedesc enters the area", respectively; you can override these
methods if a more specific message is desired.
- When modifying a class object with 'modify', the modified object
was not a class unless the 'class' keyword was included with the
'modify' statement ("modify class foo" rather than "modify foo").
This has been corrected; a modified class is still a class.
- outhide(true) now returns a status indicator, which is a value that
can be used in a subsequent call to outhide() to return output
hiding to the state it was in before the outhide(true). This
allows you to nest text hiding. When you use the nested form
(which you do simply by using the return value of outhide(true) as
the parameter - in place of nil - to the subsequent call to outhide()),
the value returned by the second outhide() indicates whether any
text output occurred ONLY BETWEEN THE NESTED CALLS. For example:
old_stat1 := outhide(true);
"This is some hidden text.";
old_stat2 := outhide(true);
// write no text here
new_stat2 := outhide(old_stat2);
new_stat1 := outhide(old_stat1);
Because outhide(old_stat2) indicates whether any output occurred
during the NESTED outhide(true), new_stat2 = nil. However, new_stat1
= true, since output occurred after the first outhide(true). Consider
another sequence:
old_stat1 := outhide(true);
// write no text here
old_stat2 := outhide(true);
"This is some hidden text.";
new_stat2 := outhide(old_stat2);
new_stat1 := outhide(old_stat1);
In this case, both new_stat1 and new_stat2 will be true, because
hidden output occurred within both nested sections.
The general form of a nested hidden output section looks like this:
{
local original_hide_stat;
local nested_stat;
original_hide_stat := outhide(true);
// do whatever you want to do while output is hidden
nested_stat := outhide(original_hide_stat);
}
Now nested_stat will indicate whether any output occurred during
the nested outhide() - that is, between the outhide(true) and
the outhide(original_hide_stat). In addition, output hiding will
be returned to the same state it was in prior to the original
outhide(true).
- The random number generator has been improved. Many people have
complained about the many undesirable properties of the old
generator, especially when small upper limits were used. The
interface to the new random number generator is the same as
before - call rand(upper_limit), which will return a uniformly
distributed random number from 1 to upper_limit, inclusive.
Note that the old random number generator will still be used if
you don't call randomize(). This allows test scripts (which require
a fixed sequence of random numbers in order to be repeatable) that
were written with older versions to continue to operate unchanged.
If you want numbers from the improved generator, be sure to call
randomize().
- When 'modify' was used on an object, the compiler sometimes did
not correctly apply the original object's vocabulary and location
to the new object. This has been corrected.
- restore() and undo() have been changed so that they always cancel
all pending commands on the command line. In the past, if the
player typed several commands, and something happened (such as
the player character dying) during one of the commands that led to
an undo() or restore(), the remaining commands were still
executed. This has been fixed.
- If you explicitly set an object's location to nil in its object
definition, and the object inherited a location from a superclass,
the system incorrectly placed the object in the contents list of
the object named in the location inherited from the object's
superclass. This has been corrected.
- "abort" can now be used within a daemon or fuse, and the
expected behavior will occur. In the past, "abort" within
a fuse (or daemon) merely exited from the current fuse,
but the remaining fuses and daemons were still executed.
Now, "abort" will cause the entire turn to end; no more
fuses or daemons will be executed on the current turn.
2.1.1 09/09/93 enhancements
- You can now access objwords(1) while deciding whether to use a
default direct object in doDefault. This is useful mostly if
you want to prevent players from being able to use "all" with
certain verbs, but still want to generate a default direct object
for the verbs. To do this, you can detect when objwords(1) = ['A']
('A' is the parser's internal code for "all", which saves you the
trouble of checking for "everything" and shorter abbreviations as
well as "all"):
doDefault(actor, prep, iobj) =
{
if (objwords(1) = ['A'])
{
global.allMessage := 'You can\'t use "all" with this verb.';
return [];
}
/* your normal default object code goes here */
}
If you wish, you can also suppress the default message that the
parser will generate ("I don't see what you're referring to").
To do this, you'll have to write your own parseError() function
and detect when an "all" violation has occurred (cleverly using the
global.allMessage, which we set above for this purpose):
parseError: function(str, num)
{
// if there's an allMessage waiting, use it instead of the default
if (global.allMessage <> nil)
{
local r;
r := global.allMessage;
global.allMessage := nil;
return r;
}
else
return nil;
}
- The compiler's error message format has been changed slightly
to work better with editors and workbench programs that scan error
logs to go to lines with errors. The format is now:
file(line): error TADS-xxxx: message
For example:
deep.t(1151): error TADS-300: expected colon
- The parser now accepts sentences of the form VERB PREP IOBJ DOBJ,
where the PREP is *not* part of the verb. For example, GIVE TO THE
MAN THE BALL. This change has two benefits. First, while this
type of sentence is not common in English, some other languages
allow this type of phrasing, so the parser is now somewhat more
adaptable to non-English languages. Second, this allows for
object defaulting and command completion when specifying just the
indirect object, which was not possible before. For example, if
the player types ASK FOR A BEER, the parser will be able to attempt
to provide a default (if one is available), or at least ask for the
direct object. Previous versions would simply say "I don't understand
that sentence." Note that the parser still attempts to combine the
verb and preposition into a single phrase; the new action happens
only when the verb and preposition don't go together (that is, they
haven't been defined together as a "verb =" property of a deepverb).
For example, suppose that a line like this appears in a deepverb:
verb = 'pick up'
In this case, PICK UP THE BOX will use THE BOX as the direct object,
just as in previous versions. Only when the verb-preposition combination
is not specifically defined in a verb will the new phrasing be used.
- When no preposition is specified between the direct and indirect objects,
the parser will now evaluate a new property, nilPrep, in the deepverb
object. This property should return the preposition object that should
be used as the preposition between the objects. Previous versions of
TADS always looked for an object that defined the word 'to' as a
"preposition =" property. While 'to' is almost always the correctly
the correct preposition to substitute in English, it's obviously the
wrong word in other languages; furthermore, the correct word in other
languages is sometimes a function of verb. If no nilPrep property is
defined for the deepverb, the parser will still use the object whose
"preposition =" property matches the word 'to'.
- The class transparentItem in adv.t has been modified so it works better
when you define an object that inherits from both transparentItem and
container or openable. First, an ldesc has been added so that the
contents of a transparentItem are listed by default with the ldesc.
Second, the "look in" command now works on a transparentItem. In
addition, the openable class has been changed so that the "look in"
command can be used when an openable is also a transparentItem, even
when the openable is closed (because you should be able to see the
contents of a transparentItem regardless of whether it's open or closed).
Thanks to Ron Hale-Evans for pointing out this problem and finding the
solution.
Please consult TADSV200.MAC (available on the High Energy BBS) for
information on earlier releases.